/*
 * Copyright © 2012-2018 Wickr Inc.  All rights reserved.
 *
 * This code is being released for EDUCATIONAL, ACADEMIC, AND CODE REVIEW PURPOSES
 * ONLY.  COMMERCIAL USE OF THE CODE IS EXPRESSLY PROHIBITED.  For additional details,
 * please see LICENSE
 *
 * THE CODE IS MADE AVAILABLE "AS-IS" AND WITHOUT ANY EXPRESS OR
 * IMPLIED GUARANTEES AS TO FITNESS, MERCHANTABILITY, NON-
 * INFRINGEMENT OR OTHERWISE. IT IS NOT BEING PROVIDED IN TRADE BUT ON
 * A VOLUNTARY BASIS ON BEHALF OF THE AUTHOR’S PART FOR THE BENEFIT
 * OF THE LICENSEE AND IS NOT MADE AVAILABLE FOR CONSUMER USE OR ANY
 * OTHER USE OUTSIDE THE TERMS OF THIS LICENSE. ANYONE ACCESSING THE
 * CODE SHOULD HAVE THE REQUISITE EXPERTISE TO SECURE THEIR SYSTEM
 * AND DEVICES AND TO ACCESS AND USE THE CODE FOR REVIEW PURPOSES
 * ONLY. LICENSEE BEARS THE RISK OF ACCESSING AND USING THE CODE. IN
 * PARTICULAR, AUTHOR BEARS NO LIABILITY FOR ANY INTERFERENCE WITH OR
 * ADVERSE EFFECT THAT MAY OCCUR AS A RESULT OF THE LICENSEE
 * ACCESSING AND/OR USING THE CODE ON LICENSEE’S SYSTEM.
 */

#ifndef crypto_engine_h
#define crypto_engine_h

#include "buffer.h"
#include "cipher.h"
#include "digest.h"
#include "eckey.h"
#include "ecdsa.h"
#include "kdf.h"

#include <stdlib.h>
#include <stdio.h>

#ifdef __cplusplus
extern "C" {
#endif

/** @addtogroup wickr_crypto_engine */

/**
 @ingroup wickr_crypto_engine
 
 @struct wickr_crypto_engine
 @brief Interface to a set of cryptographic operations used throughout the library. 
 Currently the default implementation of this can be found along with documentation in openssl_suite.h and kdf.h
 
 @var wickr_crypto_engine::default_curve 
 the curve to use when generating packets, message keys or a new context
 @var wickr_crypto_engine::default_cipher 
 the cipher to use by default for packets, local, and remote information
 */
struct wickr_crypto_engine {
    
    wickr_ec_curve_t default_curve;
    wickr_cipher_t default_cipher;
    
    /**
     @ingroup wickr_crypto_engine
     
     Generate secure random bytes
     
     @param len the number of bytes to generate
     @return a buffer containing 'len' secure random bytes or NULL if random byte generation fails
     */
    wickr_buffer_t *(*wickr_crypto_engine_crypto_random)(size_t len);
    
    /**
     @ingroup wickr_crypto_engine
     
     Generate a secure random cipher key for a particular cipher
     
     @param cipher the cipher to generate a random key for
     @return a cipher key containing key material generated by a secure random function or NULL if random byte generation fails
     */
    wickr_cipher_key_t *(*wickr_crypto_engine_cipher_key_random)(wickr_cipher_t cipher);
    
    /**
     @ingroup wickr_crypto_engine
     
     Encrypt a buffer
     
     NOTE: IV is randomly chosen using a secure random function if one is not provided
     
     @param plaintext the content to encrypt using 'key'
     @param aad additional data to authenticate with the ciphertext (only works with authenticated ciphers)
     @param key the cipher key to use to encrypt 'plaintext'
     @param iv an initialization vector to use with the cipher mode, or NULL if one should be chosen at random
     @return a cipher result containing encrypted bytes, or NULL if the cipher mode fails or is not supported
     */
    wickr_cipher_result_t *(*wickr_crypto_engine_cipher_encrypt)(const wickr_buffer_t *plaintext,
                                                                 const wickr_buffer_t *aad,
                                                                 const wickr_cipher_key_t *key,
                                                                 const wickr_buffer_t *iv);
    
    /**
     @ingroup wickr_crypto_engine
     
     Decrypt a cipher_result 
     
     @param cipher_result a cipher result generated from 'wickr_crypto_engine_cipher_encrypt'
     @param aad additional data to authenticate with the ciphertext (only works with authenticated ciphers)
     @param key the key to use to attempt to decrypt 'cipher_result'
     @param only_auth_ciphers if true, only authenticated ciphers may be used for decryption
     @return a buffer containing decrypted bytes. If the AES mode is authenticated, NULL will be returned if key is incorrect.
     */
    wickr_buffer_t *(*wickr_crypto_engine_cipher_decrypt)(const wickr_cipher_result_t *cipher_result,
                                                          const wickr_buffer_t *aad,
                                                          const wickr_cipher_key_t *key,
                                                          bool only_auth_ciphers);
    
    /**
     @ingroup wickr_crypto_engine
     
     Encrypt a file
     
     @param in_file the file to encrypt
     @param key the key to use for encryption
     @param out_file a file that should contain the encrypted data
     @return true if encryption succeeds, and 'out_file' can be written
     */
    bool (*wickr_crypto_engine_encrypt_file)(const wickr_cipher_key_t *key,
                                             const char *sourceFilePath,
                                             const char *destinationFilePath);
    
    /**
     @ingroup wickr_crypto_engine
     
     Decrypt a file
     
     @param in_file the encrypted file to decrypt
     @param key the key to use for decryption
     @param out_file the file to write the decrypted data from 'in_file'
     @param only_auth_ciphers if true, only authenticated ciphers may be used for decryption
     @return true if the decryption operation succeeds, and 'out_file' can be written
     
     */
    bool (*wickr_crypto_engine_decrypt_file)(const wickr_cipher_key_t *key,
                                             const char *sourceFilePath,
                                             const char *destinationFilePath,
                                             bool only_auth_ciphers);
    
    /**
     @ingroup wickr_crypto_engine
     
     Calculate a hash of a buffer using an optional salt value
     
     @param buffer the buffer to hash
     @param salt a salt value mix with buffer before taking the hash
     Passing NULL will allow for no salt to be used
     @param mode the mode of the hash
     @return a buffer containing the derived hash or NULL if the hashing operation fails
     */
    wickr_buffer_t *(*wickr_crypto_engine_digest)(const wickr_buffer_t *buffer,
                                                  const wickr_buffer_t *salt,
                                                  wickr_digest_t digest_mode);
    
    /**
     @ingroup wickr_crypto_engine
     
     Calculate the hash of a file
     
     @param in_file a file to take the hash of it's contents
     @param mode the mode to use for calculating the hash
     @return a buffer containing the output of the chosen mode of the contents of in_file
     */
    wickr_buffer_t *(*wickr_crypto_engine_digest_file)(FILE *in_file,
                                                       const wickr_digest_t mode);
    
    /**
     @ingroup wickr_crypto_engine
     
     Generate a random Elliptic Curve keypair
     
     @param curve the curve parameters to use for random key pair generation
     @return a random Elliptic Curve key pair or NULL if the random generation fails
     */
    wickr_ec_key_t *(*wickr_crypto_engine_ec_rand_key)(wickr_ec_curve_t curve);
    
    /**
     @ingroup wickr_crypto_engine
     
     Import an Elliptic Curve key from a buffer
     
     @param buffer the buffer representing Elliptic Curve key material
     @param is_private false if the buffer represents a public key
     @return an Elliptic Curve key pair parsed from buffer or NULL if buffer does not contain a valid key, or is_private is incorrectly set
     */
    wickr_ec_key_t *(*wickr_crypto_engine_ec_key_import)(const wickr_buffer_t *buffer,
                                                         bool is_private);
    
    /**
     @ingroup wickr_crypto_engine
     
     Sign data using an Elliptic Curve key
     Data is hashed before signing. This function will calculate ECDSA(SHA2(data_to_sign))
     
     @param ec_signing_key private signing key to use for the ECDSA algorithm
     @param data_to_sign the data to hash with 'digest_mode', and then sign with 'ec_signing_key'
     @param digest_mode the digest mode to use for SHA2
     @return an ecdsa result containing the output of ECDSA(SHA2(data_to_sign)) or NULL if the 'ec_signing_key' is not a private key
     */
    wickr_ecdsa_result_t *(*wickr_crypto_engine_ec_sign)(const wickr_ec_key_t *ec_signing_key,
                                                         const wickr_buffer_t *data_to_sign,
                                                         const wickr_digest_t digest_mode);
    
    /**
     @ingroup wickr_crypto_engine
     
     Verify ECDSA signatures
     
     @param signature a signature produced with 'wickr_crypto_engine_ec_sign'
     @param ec_public_key the public signing key to use for verification
     @param data_to_verify the original data that should have been signed with 'ec_public_key'. It will be hashed inside this function as part of the verification process
     @return true if 'signature' can be verified by 'ec_public_key'
     */
    bool (*wickr_crypto_engine_ec_verify)(const wickr_ecdsa_result_t *signature,
                                          const wickr_ec_key_t *ec_public_key,
                                          const wickr_buffer_t *data_to_verify);
    
    /**
     @ingroup wickr_crypto_engine
     
     Generate a shared secret given Elliptic Curve Diffie-Hellman parameters
     
     @param local the local elliptic curve private key
     @param peer the remote elliptic curve public key
     @return a buffer containing the shared secret computed with 'local' private key and 'peer' public key
     */
    wickr_buffer_t *(*wickr_crypto_engine_gen_shared_secret)(const wickr_ec_key_t *local, const wickr_ec_key_t *peer);
    
    /**
     @ingroup wickr_crypto_engine
     
     Generate an HMAC
     
     @param data the data to take the HMAC of
     @param hmac_key a key to use for HMAC
     @param mode the digest mode to perform HMAC with. This will determine the length of the output
     @return a buffer containing the HMAC of 'data' with 'hmac_key'
     */
    wickr_buffer_t *(*wickr_crypto_engine_hmac_create)(const wickr_buffer_t *data,
                                                       const wickr_buffer_t *hmac_key,
                                                       wickr_digest_t digest_mode);
    
    /**
     @ingroup wickr_crypto_engine
     
     Verify an HMAC against an expected result
     
     @param data the data to calculate the expected HMAC with
     @param hmac_key the key to use along with 'data' to create the expected HMAC with
     @param mode the mode to use for generating the expected HMAC
     @param expected the value to compare the generated HMAC with
     @return true if 'expected' is equal to the HMAC of 'data' and 'hmac_key'
     */
    bool (*wickr_crypto_engine_hmac_verify)(const wickr_buffer_t *data,
                                            const wickr_buffer_t *hmac_key,
                                            const wickr_digest_t mode,
                                            const wickr_buffer_t *expected);
    
    /**
     
     @ingroup wickr_crypto_engine
     
     Execute a KDF function given an input buffer
     
     @param algo the algorithm info to use for execution of the KDF
     @param passphrase bytes to use as input to the KDF function. There are no restrictions for the content of the buffer
     @return the output of the KDF function, including the generated random salt that was used for the computation
     */
    wickr_kdf_result_t *(*wickr_crypto_kdf_gen)(wickr_kdf_algo_t algo,
                                                const wickr_buffer_t *passphrase);
    
    /**
     
     @ingroup wickr_crypto_engine
     
     Execute a KDF function given an input buffer and specified parameters
     
     @param existing_meta the parameters to use for execution, including a specific salt
     @param passphrase bytes to use as input to the KDF function. There are no restrictions for the content of the buffer
     @return the output of the KDF function, including the generated random salt that was used for the computation
     */
    wickr_kdf_result_t *(*wickr_crypto_kdf_meta)(const wickr_kdf_meta_t *existing_meta,
                                                 const wickr_buffer_t *passphrase);
};

typedef struct wickr_crypto_engine wickr_crypto_engine_t;

/**
 @ingroup wickr_crypto_engine

 Wickr default crypto engine
 
 @return an engine containing default crypto primitive implementations (currently implemented with OpenSSL 1.0.2, libscrypt, libbcrypt)
 */
const wickr_crypto_engine_t wickr_crypto_engine_get_default(void);

/**
 @ingroup wickr_crypto_engine
 
 Encrypt a buffer with a KDF + CIPHER
 
 The KDF + CIPHER functions currently acts as follows:
 
 1. Calculate KDF(randomSalt || passphrase)
 2. Use the output of step 1. as a cipher key to encrypt the buffer 'value' with the default engine cipher
 3. The output of step 2 is then packed as | KDF_ID | RANDOM_SALT | CIPHER_TEXT |
 
 @param engine the engine to use for ciphering / kdf functions
 @param algo the kdf algorithm to use for key derivation
 @param cipher the cipher to use to encrypt 'value' with the output of the KDF function with 'passphrase' as input
 @param value the value to protect with the KDF cipher
 @param passphrase the KDF input to use for getting a cipher key
 @return a buffer serialized in the following format:
    
 */
wickr_buffer_t *wickr_crypto_engine_kdf_cipher(const wickr_crypto_engine_t *engine,
                                               wickr_kdf_algo_t algo,
                                               wickr_cipher_t cipher,
                                               const wickr_buffer_t *value,
                                               const wickr_buffer_t *passphrase);

/**
 @ingroup wickr_crypto_engine

 @param engine the engine to use for deciphering / kdf functions
 @param input_buffer the output of a kdf + cipher operation with 'wickr_crypto_engine_kdf_cipher'
 @param passphrase the passphrase for the kdf + cipher operation
 @return the original buffer protected by 'wickr_crypto_engine_kdf_cipher' or NULL if the KDF + cipher function fails due to an incorrect passphrase
 */
wickr_buffer_t *wickr_crypto_engine_kdf_decipher(const wickr_crypto_engine_t *engine,
                                                 const wickr_buffer_t *input_buffer,
                                                 const wickr_buffer_t *passphrase);

/**
 @ingroup wickr_crypto_engine

 
 Get the matching digest type for a function based on size
 
 DEPRECATED IN FAVOR OF wickr_exchange_kdf_matching_cipher
 
 NOTE: Currently only 256bit AES ciphers are supported, so this function always returns SHA_256
  
 @param cipher the cipher to find the matching digest for
 @return a digest that has an output which is the same size as the length of the cipher's key
 */
wickr_digest_t wickr_digest_matching_cipher(wickr_cipher_t cipher);

/**
 @ingroup wickr_crypto_engine

 Get the matching digest for a curve, this is to be used for signature operations using this curve
 
 @param curve a curve to get the matching digest for
 @return the digest to use for signature operations using 'curve'
 */
wickr_digest_t wickr_digest_matching_curve(wickr_ec_curve_t curve);

/**
 @ingroup wickr_crypto_engine

 Get the matching exchange cipher given a message packet cipher
 
 An exchange cipher is used for wrapping / unwrapping packet content decryption key material (see wickr_key_exchange_create_with_packet_key)
 This function currently always returns CIPHER_ID_AES256_CTR
 The lack of authentication on this layer is a performance / space optimization, since it is ultimately protecting authenticated mode key material (currently always CIPHER_ID_AES256_GCM) to be used for packet content decryption
 If bits are flipped in the key exchange itself, the resulting unauthenticated output will not be able to decrypt the GCM mode packet content
 
 @param cipher the cipher being used for packet content encryption / decryption
 @return the exchange cipher matching 'cipher'
 */
wickr_cipher_t wickr_exchange_cipher_matching_cipher(wickr_cipher_t cipher);

#ifdef __cplusplus
}
#endif

#endif /* crypto_engine_h */
